// Copyright 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/output/program_binding.h"

#include "base/trace_event/trace_event.h"
#include "cc/output/geometry_binding.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/gles2_interface.h"

using gpu::gles2::GLES2Interface;

namespace cc {

ProgramKey::ProgramKey() = default;
ProgramKey::ProgramKey(const ProgramKey& other) = default;
ProgramKey::~ProgramKey() = default;

bool ProgramKey::operator==(const ProgramKey& other) const
{
    return type_ == other.type_ && precision_ == other.precision_ && sampler_ == other.sampler_ && blend_mode_ == other.blend_mode_ && aa_mode_ == other.aa_mode_ && swizzle_mode_ == other.swizzle_mode_ && is_opaque_ == other.is_opaque_ && premultiplied_alpha_ == other.premultiplied_alpha_ && has_background_color_ == other.has_background_color_ && mask_mode_ == other.mask_mode_ && mask_for_background_ == other.mask_for_background_ && has_color_matrix_ == other.has_color_matrix_ && yuv_alpha_texture_mode_ == other.yuv_alpha_texture_mode_ && uv_texture_mode_ == other.uv_texture_mode_ && color_conversion_mode_ == other.color_conversion_mode_;
}

// static
ProgramKey ProgramKey::DebugBorder()
{
    ProgramKey result;
    result.type_ = PROGRAM_TYPE_DEBUG_BORDER;
    return result;
}

// static
ProgramKey ProgramKey::SolidColor(AAMode aa_mode)
{
    ProgramKey result;
    result.type_ = PROGRAM_TYPE_SOLID_COLOR;
    result.aa_mode_ = aa_mode;
    return result;
}

// static
ProgramKey ProgramKey::Tile(TexCoordPrecision precision,
    SamplerType sampler,
    AAMode aa_mode,
    SwizzleMode swizzle_mode,
    bool is_opaque)
{
    ProgramKey result;
    result.type_ = PROGRAM_TYPE_TILE;
    result.precision_ = precision;
    result.sampler_ = sampler;
    result.aa_mode_ = aa_mode;
    result.swizzle_mode_ = swizzle_mode;
    result.is_opaque_ = is_opaque;
    return result;
}

// static
ProgramKey ProgramKey::Texture(TexCoordPrecision precision,
    SamplerType sampler,
    PremultipliedAlphaMode premultiplied_alpha,
    bool has_background_color)
{
    ProgramKey result;
    result.type_ = PROGRAM_TYPE_TEXTURE;
    result.precision_ = precision;
    result.sampler_ = sampler;
    result.premultiplied_alpha_ = premultiplied_alpha;
    result.has_background_color_ = has_background_color;
    return result;
}

// static
ProgramKey ProgramKey::RenderPass(TexCoordPrecision precision,
    SamplerType sampler,
    BlendMode blend_mode,
    AAMode aa_mode,
    MaskMode mask_mode,
    bool mask_for_background,
    bool has_color_matrix)
{
    ProgramKey result;
    result.type_ = PROGRAM_TYPE_RENDER_PASS;
    result.precision_ = precision;
    result.sampler_ = sampler;
    result.blend_mode_ = blend_mode;
    result.aa_mode_ = aa_mode;
    result.mask_mode_ = mask_mode;
    result.mask_for_background_ = mask_for_background;
    result.has_color_matrix_ = has_color_matrix;
    return result;
}

// static
ProgramKey ProgramKey::VideoStream(TexCoordPrecision precision)
{
    ProgramKey result;
    result.type_ = PROGRAM_TYPE_VIDEO_STREAM;
    result.precision_ = precision;
    result.sampler_ = SAMPLER_TYPE_EXTERNAL_OES;
    return result;
}

// static
ProgramKey ProgramKey::YUVVideo(TexCoordPrecision precision,
    SamplerType sampler,
    YUVAlphaTextureMode yuv_alpha_texture_mode,
    UVTextureMode uv_texture_mode,
    ColorConversionMode color_conversion_mode)
{
    ProgramKey result;
    result.type_ = PROGRAM_TYPE_YUV_VIDEO;
    result.precision_ = precision;
    result.sampler_ = sampler;
    result.yuv_alpha_texture_mode_ = yuv_alpha_texture_mode;
    DCHECK(yuv_alpha_texture_mode == YUV_NO_ALPHA_TEXTURE || yuv_alpha_texture_mode == YUV_HAS_ALPHA_TEXTURE);
    result.uv_texture_mode_ = uv_texture_mode;
    DCHECK(uv_texture_mode == UV_TEXTURE_MODE_UV || uv_texture_mode == UV_TEXTURE_MODE_U_V);
    result.color_conversion_mode_ = color_conversion_mode;
    return result;
}

ProgramBindingBase::ProgramBindingBase()
    : program_(0)
    , vertex_shader_id_(0)
    , fragment_shader_id_(0)
    , initialized_(false)
{
}

ProgramBindingBase::~ProgramBindingBase()
{
    // If you hit these asserts, you initialized but forgot to call Cleanup().
    DCHECK(!program_);
    DCHECK(!vertex_shader_id_);
    DCHECK(!fragment_shader_id_);
    DCHECK(!initialized_);
}

bool ProgramBindingBase::Init(GLES2Interface* context,
    const std::string& vertex_shader,
    const std::string& fragment_shader)
{
    TRACE_EVENT0("cc", "ProgramBindingBase::init");
    vertex_shader_id_ = LoadShader(context, GL_VERTEX_SHADER, vertex_shader);
    if (!vertex_shader_id_)
        return false;

    fragment_shader_id_ = LoadShader(context, GL_FRAGMENT_SHADER, fragment_shader);
    if (!fragment_shader_id_) {
        context->DeleteShader(vertex_shader_id_);
        vertex_shader_id_ = 0;
        return false;
    }

    program_ = CreateShaderProgram(context, vertex_shader_id_, fragment_shader_id_);
    return !!program_;
}

bool ProgramBindingBase::Link(GLES2Interface* context)
{
    context->LinkProgram(program_);
    CleanupShaders(context);
    if (!program_)
        return false;
#ifndef NDEBUG
    int linked = 0;
    context->GetProgramiv(program_, GL_LINK_STATUS, &linked);
    if (!linked) {
        char buffer[1024] = "";
        context->GetProgramInfoLog(program_, sizeof(buffer), nullptr, buffer);
        DLOG(ERROR) << "Error compiling shader: " << buffer;
        return false;
    }
#endif
    return true;
}

void ProgramBindingBase::Cleanup(GLES2Interface* context)
{
    initialized_ = false;
    if (!program_)
        return;

    DCHECK(context);
    context->DeleteProgram(program_);
    program_ = 0;

    CleanupShaders(context);
}

unsigned ProgramBindingBase::LoadShader(GLES2Interface* context,
    unsigned type,
    const std::string& shader_source)
{
    unsigned shader = context->CreateShader(type);
    if (!shader)
        return 0u;

    const char* shader_source_str[] = { shader_source.data() };
    int shader_length[] = { static_cast<int>(shader_source.length()) };
    context->ShaderSource(
        shader, 1,
        shader_source_str,
        shader_length);
    context->CompileShader(shader);
#if DCHECK_IS_ON()
    int compiled = 0;
    context->GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if (!compiled) {
        char buffer[1024] = "";
        context->GetShaderInfoLog(shader, sizeof(buffer), nullptr, buffer);
        DLOG(ERROR) << "Error compiling shader: " << buffer
                    << "\n shader program: " << shader_source;
        return 0u;
    }
#endif
    return shader;
}

unsigned ProgramBindingBase::CreateShaderProgram(GLES2Interface* context,
    unsigned vertex_shader,
    unsigned fragment_shader)
{
    unsigned program_object = context->CreateProgram();
    if (!program_object)
        return 0;

    context->AttachShader(program_object, vertex_shader);
    context->AttachShader(program_object, fragment_shader);

    // Bind the common attrib locations.
    context->BindAttribLocation(
        program_object, GeometryBinding::PositionAttribLocation(), "a_position");
    context->BindAttribLocation(
        program_object, GeometryBinding::TexCoordAttribLocation(), "a_texCoord");
    context->BindAttribLocation(program_object,
        GeometryBinding::TriangleIndexAttribLocation(),
        "a_index");

    return program_object;
}

void ProgramBindingBase::CleanupShaders(GLES2Interface* context)
{
    if (vertex_shader_id_) {
        context->DeleteShader(vertex_shader_id_);
        vertex_shader_id_ = 0;
    }
    if (fragment_shader_id_) {
        context->DeleteShader(fragment_shader_id_);
        fragment_shader_id_ = 0;
    }
}

bool ProgramBindingBase::IsContextLost(GLES2Interface* context)
{
    return context->GetGraphicsResetStatusKHR() != GL_NO_ERROR;
}

} // namespace cc
